﻿using Common.Cache;
using Common.Encrypt;
using Common.Exceptions;
using Common.Extension;
using Common.Web;
using Common.Web.Authentication;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Primitives;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;

namespace Microsoft.AspNetCore.Mvc
{
    /// <summary>
    /// 
    /// </summary>
    public static class JwtTokenExtensionFunctions
    {
        private static string DecodeTokenItem(string cipher)
        {
            if (string.IsNullOrEmpty(cipher) || cipher.Length <= 1)
            {
                return cipher;
            }
            cipher = cipher[1..];
            return Encoding.UTF8.GetString(Base58.Decode(cipher));
        }
        /// <summary>
        /// 获取登录用户信息
        /// </summary>
        /// <param name="principal"></param>
        /// <exception cref="LoginInfoNotFoundException">登录信息未找到</exception>
        /// <returns>未登录时抛出异常<see cref="LoginInfoNotFoundException"/>，全局异常捕捉会为此异常输出401</returns>
        public static LoginUser GetLoginUser(this ClaimsPrincipal principal)
        {
            LoginUser user = new LoginUser();
            user.UserId = principal.GetUserId();

            user.LoginTime = principal.GetLoginTime();
            user.StaffID = DecodeTokenItem(principal.Claims.FirstOrDefault(x => x.Type == "stid")?.Value);
            user.SubjectId = DecodeTokenItem(principal.Claims.FirstOrDefault(x => x.Type == "sid")?.Value);
            user.SubjectType = principal.Claims.FirstOrDefault(x => x.Type == "stp")?.Value;
            user.Ticket = principal.GetUserTicket();
            user.LoginAppId = principal.GetAudience();
            user.Scopes = principal.GetScope();
            return user;
        }
        /// <summary>
        /// 获取登录用户信息
        /// </summary>
        /// <param name="controller"></param>
        /// <exception cref="LoginInfoNotFoundException">登录信息未找到</exception>
        /// <returns>未登录时抛出异常<see cref="LoginInfoNotFoundException"/>，全局异常捕捉会为此异常输出401</returns>
        public static LoginUser GetLoginUser(this ControllerBase controller)
        {
            var user = controller.User.GetLoginUser();
            if (string.IsNullOrEmpty(user.SubjectId))
            {
                BaseUserDTO xtokenUser;
                if (!controller.Request.Headers.TryGetValue("x-token", out StringValues xtoken) || (xtokenUser = xTokenHelper.Get(xtoken)) == null)
                {
                    throw new LoginInfoNotFoundException("登录已过期，请重新登录[x-token]");
                }
                SettleAccountType accountType = xtokenUser.UserType.ToEnum<SettleAccountType>();
                user.StaffID = xtokenUser.PersonID;
                user.SubjectType = accountType.GetHashCode().ToString();
                user.SubjectId = accountType == SettleAccountType.merchant ? xtokenUser.MerchantID : xtokenUser.OrganizationID;
            }
            return user;
        }
        #region ClaimsPrincipal扩展
        /// <summary>
        /// 获取当前Token的登录时间，如果没有登录信息或者token里未包含登录时间返回空
        /// </summary>
        /// <param name="principal"></param>
        /// <returns>未登录返回null</returns>
        public static DateTime? GetLoginTime(this ClaimsPrincipal principal)
        {
            if (!principal.IsLogined())
            {
                return null;
            }
            string nbf = principal.Claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Nbf)?.Value;
            if (string.IsNullOrEmpty(nbf))
            {
                return null;
            }
            if (!long.TryParse(nbf, out long timestamp))
            {
                return null;
            }
            return DateTimeOffset.FromUnixTimeSeconds(timestamp).DateTime;
        }
        /// <summary>
        /// 是否已登录
        /// </summary>
        /// <param name="principal"></param>
        /// <returns></returns>
        public static bool IsLogined(this ClaimsPrincipal principal)
        {
            return principal != null && principal.Claims != null && principal.Claims.Any();
        }
        /// <summary>
        /// 获取当前登录的用户ID
        /// </summary>
        /// <param name="principal"></param>
        /// <exception cref="LoginInfoNotFoundException">未找到登录信息</exception>
        /// <returns>未登录时抛出异常<see cref="LoginInfoNotFoundException"/>，全局异常捕捉会为此异常输出401</returns>
        public static long GetUserId(this ClaimsPrincipal principal)
        {
            if (principal == null || principal.Claims == null || principal.Claims.Count() <= 0)
            {
                throw new LoginInfoNotFoundException("未找到登录信息");
            }
            string cipher = principal.Claims.FirstOrDefault(c => c.Type == JwtTokenHelper.USERID_NAME)?.Value;
            if (string.IsNullOrEmpty(cipher))
            {
                throw new LoginInfoNotFoundException("登录信息有误");
            }
            string uid = Encoding.UTF8.GetString(Base58.Decode(cipher.Substring(1)));
            if (!long.TryParse(uid, out long userId))
            {
                throw new LoginInfoNotFoundException("登录信息有误");
            }
            return userId;
        }
        /// <summary>
        /// 获取登录应用
        /// </summary>
        /// <param name="principal"></param>
        /// <returns>未登录返回null</returns>
        public static string GetAudience(this ClaimsPrincipal principal)
        {
            if (principal == null || principal.Claims == null || principal.Claims.Count() <= 0)
            {
                return null;
            }
            return principal.Claims.FirstOrDefault(x => x.Type == JwtTokenHelper.AUD_NAME)?.Value;
        }
        /// <summary>
        /// 获取用户登录端口
        /// </summary>
        /// <param name="principal"></param>
        /// <returns>未登录返回null</returns>
        public static string GetScope(this ClaimsPrincipal principal)
        {
            if (principal == null || principal.Claims == null || principal.Claims.Count() <= 0)
            {
                return null;
            }
            return principal.Claims.FirstOrDefault(x => x.Type == JwtTokenHelper.SCOPE_NAME)?.Value;
        }
        /// <summary>
        /// 获取用户登录票证
        /// </summary>
        /// <param name="principal"></param>
        /// <returns>未登录返回null</returns>
        public static string GetUserTicket(this ClaimsPrincipal principal)
        {
            if (principal == null || principal.Claims == null || principal.Claims.Count() <= 0)
            {
                return null;
            }
            return principal.Claims.FirstOrDefault(x => x.Type == "ticket")?.Value;
        }
        /// <summary>
        /// 获取当前登录的用户名
        /// </summary>
        /// <param name="principal"></param>
        /// <returns>未登录返回null</returns>
        [Obsolete("JWT不再提供用户名，此方法将一直返回null，请删除")]
        public static string GetUserName(this ClaimsPrincipal principal)
        {
            if (principal == null || principal.Claims == null || principal.Claims.Count() <= 0)
            {
                return null;
            }
            string name = principal.Claims.FirstOrDefault(n => n.Type == JwtTokenHelper.USER_NAME)?.Value;
            if (string.IsNullOrEmpty(name))
            {
                return null;
            }
            return Encoding.UTF8.GetString(Base58.Decode(name.Substring(1)));
        }
        #endregion

        /// <summary>
        /// 是否已登录
        /// </summary>
        /// <param name="controller"></param>
        /// <returns></returns>
        public static bool IsLogined(this ControllerBase controller)
        {
            return controller.User.IsLogined();
        }
        /// <summary>
        /// 获取当前Token的登录时间，如果没有登录信息或者token里未包含登录时间返回空
        /// </summary>
        /// <param name="controller"></param>
        /// <returns></returns>
        public static DateTime? GetLoginTime(this ControllerBase controller)
        {
            return controller.User.GetLoginTime();
        }
        /// <summary>
        /// 获取用户登录票证
        /// </summary>
        /// <param name="controller"></param>
        /// <returns></returns>
        public static string GetUserTicket(this ControllerBase controller)
        {
            return controller.User.GetUserTicket();
        }
        /// <summary>
        /// 获取当前登录的用户ID
        /// </summary>
        /// <param name="controller"></param>
        /// <exception cref="LoginInfoNotFoundException">未找到登录信息</exception>
        /// <returns>未登录时抛出异常<see cref="LoginInfoNotFoundException"/>，全局异常捕捉会为此异常输出401</returns>
        public static long GetUserId(this ControllerBase controller)
        {
            if (!controller.IsLogined() && !controller.Request.Headers.TryGetValue("auth-code", out StringValues authCode))
            {
                throw new LoginInfoNotFoundException("未找到登录信息");
            }
            return controller.IsLogined() ? controller.User.GetUserId() : DecodeAuthCode(authCode);
        }
        /// <summary>
        /// 获取当前登录的用户名称
        /// </summary>
        /// <param name="controller"></param>
        /// <exception cref="LoginInfoNotFoundException">未找到登录信息</exception>
        /// <returns></returns>
        [Obsolete("JWT不再提供用户名，此方法将一直返回null，请删除")]
        public static string GetUserName(this ControllerBase controller)
        {
            return controller.User.GetUserName();
        }
        /// <summary>
        /// 获取登录应用
        /// </summary>
        /// <param name="controller"></param>
        /// <returns></returns>
        public static string GetAudience(this ControllerBase controller)
        {
            return controller.User.GetAudience();
        }
        /// <summary>
        /// 获取用户登录端口
        /// </summary>
        /// <param name="controller"></param>
        /// <returns></returns>
        public static string GetScope(this ControllerBase controller)
        {
            return controller.User.GetScope();
        }

        /// <summary>
        /// 添加Jwt令牌验证。颁发者（TokenIssuer，不配置默认为sso），接受者(TokenAudience)，令牌秘钥(TokenSecret)从配置管理器(IConfiguration)读取
        /// </summary>
        /// <param name="services"></param>
        /// <param name="configuration">配置管理器，读取颁发者（TokenIssuer）,接受者（TokenAudience）,令牌秘钥（TokenSecret）</param>
        public static void AddJwtAuthentication(this IServiceCollection services, IConfiguration configuration)
        {
            string appId = configuration["TokenAudience"];
            string tokenSecret = configuration["TokenSecret"];
            string issuer = configuration["TokenIssuer"] ?? "sso";
            string redis = configuration["redis"] ?? throw new MissingConfigurationException("redis");
            JwtTokenHelper.Redis = FreeRedisCache.Instance;
            JwtTokenHelper.Issuer = issuer;
            JwtTokenHelper.Audiences = appId?.Split(',');
            JwtTokenHelper.Secret = tokenSecret;

            var tokenValidationParameters = new TokenValidationParameters
            {
                ValidateLifetime = true,
                ValidateAudience = JwtTokenHelper.Audiences != null && JwtTokenHelper.Audiences.Any(),
                ValidateIssuer = true,
                //时钟偏移
                ClockSkew = TimeSpan.FromSeconds(30),
                ValidateIssuerSigningKey = true,
                ValidIssuer = issuer,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenSecret)),
                LifetimeValidator = ValidateLifetime
            };
            if (tokenValidationParameters.ValidateAudience)
            {
                tokenValidationParameters.ValidAudiences = JwtTokenHelper.Audiences;
            }
            Utils.WriteConsole($"appId={appId}&tokenSecret={tokenSecret}&issuer={issuer}");
            services.AddAuthentication(x =>
            {
                x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = nameof(JwtTokenChallengeHandler);
                x.DefaultForbidScheme = nameof(JwtTokenChallengeHandler);
                //x.DefaultAuthenticateScheme = nameof(JwtTokenChallengeHandler);
            })
            .AddJwtBearer(setup =>
            {
                setup.TokenValidationParameters = tokenValidationParameters;
                setup.Events = new JwtBearerEvents
                {
                    OnChallenge = ctx =>
                    {
                        Utils.WriteConsole("setup.OnChallenge");
                        ctx.HandleResponse();
                        ctx.Response.Headers.Add("Token-Invalid", ctx.ErrorDescription);
                        return Task.CompletedTask;
                    },
                    OnTokenValidated = ctx =>
                    {
                        ctx.Response.Headers.Add("Token-Validated", "true");
                        return Task.CompletedTask;
                    }
                };
            })
            .AddScheme<AuthenticationSchemeOptions, JwtTokenChallengeHandler>(nameof(JwtTokenChallengeHandler), aso => { });
        }
        /// <summary>
        /// 验证token是否有效
        /// </summary>
        /// <param name="notBefore"></param>
        /// <param name="expires"></param>
        /// <param name="securityToken"></param>
        /// <param name="validationParameters"></param>
        /// <returns></returns>
        private static bool ValidateLifetime(DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters)
        {
            var jwt = securityToken as JwtSecurityToken;
            string userId = jwt.Claims?.FirstOrDefault(x => x.Type == JwtTokenHelper.USERID_NAME)?.Value;
            string appid = jwt.Claims?.FirstOrDefault(x => x.Type == JwtTokenHelper.AUD_NAME)?.Value;
            if (JwtTokenHelper.IsTokenBanned(userId, appid, notBefore, out _))
            {
                return false;
            }
            var parameters = validationParameters.Clone();
            parameters.LifetimeValidator = null;
            Validators.ValidateLifetime(notBefore, expires, securityToken, parameters);
            return true;
        }

        private static long DecodeAuthCode(string authCode)
        {
            authCode = authCode[1..];//首字符为版本号，去掉
            byte[] buf = Base58.Decode(authCode);
            string plain = Encoding.UTF8.GetString(buf[..^1]);
            string[] arr = plain.Split(':');
            long userId = long.Parse(arr[1]);
            return userId;
        }
    }
}
